package wangwenjun.phase3.executor.completablefuture.demo01;

import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import util.ThreadUtil;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

/**
 * @author ChangLiang
 * @date 2020/6/24
 */
@Slf4j
public class AppV2Test {

    /**
     * 场景2：需要根据mobile到user_table以及device_table中获取到相关的内容 组装为一个大的DTO对象
     */
    private String mobile = "13233333333";

    @Test
    @DisplayName("test sync execute")
    public void test() {
        /**
         * main线程同步执行
         * 如果想实现场景2的异步 那么就不能使用thenXXX方法 分阶段执行的时候才需要thenXXX ???
         */
        log.warn("start");
        UserAndDeviceDto result = new UserAndDeviceDto();
        CompletableFuture.completedFuture(DbService.queryUserWithMobile(mobile))
                .thenAcceptBoth(
                        CompletableFuture.completedFuture(DbService.queryDeviceWithMobile(mobile)),
                        (user, device) -> {
                            result.setDeviceNo(device.getDeviceNo());
                            result.setMobile(mobile);
                            result.setName(user.getName());
                        });
        log.warn("result:{}", result.toString());
    }

    @Test
    @DisplayName("test async execute")
    public void test2() throws InterruptedException {
        /**
         * 这样虽然是异步执行了 但是真实场景下 没有办法返回
         * 也就是只有在不需要返回值的情况下才能使用
         * 另外，这种thenAccept 就是分阶段执行的
         */
        log.warn("start");
        UserAndDeviceDto result = new UserAndDeviceDto();
        CompletableFuture.completedFuture(DbService.queryUserWithMobile(mobile))
                .thenAcceptBothAsync(
                        CompletableFuture.supplyAsync(() -> DbService.queryDeviceWithMobile(mobile)),
                        (user, device) -> {
                            result.setDeviceNo(device.getDeviceNo());
                            result.setMobile(mobile);
                            result.setName(user.getName());
                            log.error("result:{}", result.toString());
                        });
        log.warn("result:{}", result.toString());
        Thread.currentThread().join();
    }

    @Test
    @DisplayName("test")
    public void test3() {
        /**
         * 两个future分别执行 都执行完成后进行回调
         */

        log.warn("start");
        UserAndDeviceDto result = new UserAndDeviceDto();
        CompletableFuture<Void> voidCompletableFuture = CompletableFuture.allOf(
                CompletableFuture.supplyAsync(() -> DbService.queryUserWithMobile(mobile))
                        .exceptionally(t->{
                            log.error(ThreadUtil.exceptionToString(t));
                            log.error("queryUserWithMobile fails, mobile:{}", mobile);
                            return new UserObject();
                        })
                        .whenComplete((v, t) -> {
                            if (t == null) {
                                result.setName(v.getName());
                                result.setMobile(v.getMobile());
                            } else {
                                ThreadUtil.exceptionToString(t);
                            }
                        })
                ,
                CompletableFuture.supplyAsync(() -> DbService.queryDeviceWithMobile(mobile))
                        .exceptionally(t->{
                            log.error(ThreadUtil.exceptionToString(t));
                            log.error("queryDeviceWithMobile fails, mobile:{}", mobile);
                            return new DeviceObject();
                        })
                        .whenComplete((v, t) -> {
                            if (t == null) {
                                result.setDeviceNo(v.getDeviceNo());
                            } else {
                                ThreadUtil.exceptionToString(t);
                            }
                        })
        );
        voidCompletableFuture.join();
        log.warn("result:{}", result);
    }

    @Test
    @DisplayName("test")
    public void test4() {
        /**
         * 两个future分别执行 都执行完成后进行回调
         * thenCombineAsync 看起来只适用于两个Future
         * 加入了超时
         */

        log.warn("start");
        UserAndDeviceDto result = new UserAndDeviceDto();
        CompletableFuture<Void> voidCompletableFuture = CompletableFuture.allOf(
                CompletableFuture.supplyAsync(() -> DbService.queryUserWithMobile(mobile))
                        .exceptionally(t->{
                            log.error(ThreadUtil.exceptionToString(t));
                            log.error("queryUserWithMobile fails, mobile:{}", mobile);
                            return new UserObject();
                        })
                        .whenComplete((v, t) -> {
                            if (t == null) {
                                result.setName(v.getName());
                                result.setMobile(v.getMobile());
                            } else {
                                ThreadUtil.exceptionToString(t);
                            }
                        })
                ,
                CompletableFuture.supplyAsync(() -> DbService.queryDeviceWithMobile(mobile))
                        .whenComplete((v, t) -> {
                            if (t == null) {
                                result.setDeviceNo(v.getDeviceNo());
                            } else {
                                ThreadUtil.exceptionToString(t);
                            }
                        })
        );
        try {
            voidCompletableFuture.get(5, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        }
        log.warn("result:{}", result);
    }

    @Test
    @DisplayName("test withCombine")
    public void test5() {
        /**
         * 其使用ForkJoinPool的不同的线程进行执行
         *
         */

        log.warn("start");
        UserAndDeviceDto result = new UserAndDeviceDto();

        CompletableFuture<UserAndDeviceDto> future = CompletableFuture.supplyAsync(() -> DbService.queryUserWithMobile(mobile))
                .exceptionally(t -> {
                    log.error(ThreadUtil.exceptionToString(t));
                    log.error("queryUserWithMobile fails, mobile:{}", mobile);
                    return new UserObject();
                })
                .whenComplete((v, t) -> {
                    if (t == null) {
                        result.setName(v.getName());
                        result.setMobile(v.getMobile());
                    } else {
                        ThreadUtil.exceptionToString(t);
                    }
                }).thenCombine(
                        CompletableFuture.supplyAsync(() -> DbService.queryDeviceWithMobile(mobile))
                                .whenComplete((v, t) -> {
                                    if (t == null) {
                                        result.setDeviceNo(v.getDeviceNo());
                                    } else {
                                        ThreadUtil.exceptionToString(t);
                                    }
                                }), (user, device) -> {
                            result.setDeviceNo(device.getDeviceNo());
                            result.setMobile(device.getMobile());
                            result.setName(user.getName());
                            return result;
                        }
                );
        try {
            future.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        log.warn("result:{}", result);
    }

    @Test
    @DisplayName("test withCombineAsync")
    public void test6() {
        /**
         * 其使用ForkJoinPool的不同的线程进行执行
         * 可能supplyAsync就决定了使用不同线程
         */

        log.warn("start");
        UserAndDeviceDto result = new UserAndDeviceDto();

        CompletableFuture<UserAndDeviceDto> future = CompletableFuture.supplyAsync(() -> DbService.queryUserWithMobile(mobile))
                .exceptionally(t -> {
                    log.error(ThreadUtil.exceptionToString(t));
                    log.error("queryUserWithMobile fails, mobile:{}", mobile);
//                    return new UserObject();
                    // or
                    throw new RuntimeException(t.getMessage());
                })
                .thenCombineAsync(
                        CompletableFuture.supplyAsync(() -> DbService.queryDeviceWithMobile(mobile))
                                .whenComplete((v, t) -> {
                                    if (t == null) {
                                        result.setDeviceNo(v.getDeviceNo());
                                    } else {
                                        ThreadUtil.exceptionToString(t);
                                    }
                                }), (user, device) -> {
                            result.setDeviceNo(device.getDeviceNo());
                            result.setMobile(device.getMobile());
                            result.setName(user.getName());
                            return result;
                        }
                );
        try {
            future.get(3,TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        }
        log.warn("result:{}", result);
    }
}
